﻿using System.Globalization;
using Devonline.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 文件操作类服务
/// </summary>
/// <param name="logger">日志</param>
/// <param name="httpSetting">Http配置项</param>
public class FileService(ILogger<FileService> logger, HttpSetting httpSetting) : IFileService
{
    protected readonly ILogger<FileService> _logger = logger;
    protected readonly HttpSetting _httpSetting = httpSetting;

    /// <summary>
    /// 从 fileName 获取文件扩展名为 extension 的同名文件
    /// </summary>
    /// <param name="fileName">原始文件名</param>
    /// <param name="extension">文件扩展名</param>
    /// <returns></returns>
    public virtual string GetSameFileName(string fileName, string extension) => Path.Combine(Path.GetDirectoryName(fileName)!, Path.GetFileNameWithoutExtension(fileName))! + extension;
    /// <summary>
    /// 获取附件绝对地址
    /// </summary>
    /// <param name="fileName">附件文件名</param>
    /// <returns></returns>
    public virtual string GetAttachmentPath(string fileName) => Path.Combine((_httpSetting.Attachment?.RootPath ?? AppSettings.DEFAULT_ATTACHMENT_PATH).GetAbsolutePath(), fileName);
    /// <summary>
    /// 获取随机的临时文件名
    /// </summary>
    /// <param name="extension">文件扩展名</param>
    /// <returns></returns>
    public virtual string GetTempFileName(string? extension = default)
    {
        var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        extension ??= AppSettings.DEFAULT_TEMPORARY_FILE_EXTENSION;
        return Path.Combine(filePath, KeyGenerator.GetKey() + extension);
    }
    /// <summary>
    /// 从文件名获取文件绝对地址, 如果文件名是来自互联网的路径, 则下载并保存到本地在返回
    /// </summary>
    /// <param name="fileName">相对文件名或互联网路径</param>
    /// <returns></returns>
    public async Task<string> GetFileAsync(string fileName)
    {
        var filePath = GetAttachmentPath(fileName);
        if (File.Exists(filePath))
        {
            return filePath;
        }

        //如果是一个互联网地址, 则先下载到本地文件
        if (fileName.Contains(AppSettings.DEFAULT_PROTOCOL_SPLITER))
        {
            var fileExtension = Path.GetExtension(fileName);
            var allowFileExtensions = (_httpSetting.Attachment.Download.Extensions ?? AppSettings.DEFAULT_ALLOW_FILE_EXTENSIONS).Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
            if (!allowFileExtensions.Contains(fileExtension))
            {
                throw new FileLoadException($"不允许下载的文件扩展名: " + fileExtension, fileName);
            }

            using var httpClient = new HttpClient();
            using var stream = await httpClient.GetStreamAsync(fileName);
            if (stream.CanRead && stream.Length > 0)
            {
                if (stream.Length > _httpSetting.Attachment.Download.Size)
                {
                    stream.Close();
                    throw new BadHttpRequestException($"文件 {fileName} 大小超过允许下载的文件大小限制!");
                }

                fileName = GetTempFileName(fileExtension);
                fileName = GetAttachmentPath(fileName);
                using var fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
                await stream.CopyToAsync(fileStream);
                await fileStream.FlushAsync();
                stream.Close();
                fileStream.Close();
            }

            return fileName;
        }

        throw new BadHttpRequestException($"文件 {fileName} 不存在!");
    }

    /// <summary>
    /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="fileName">待删除的文件</param>
    public virtual void DeleteFile(string fileName)
    {
        try
        {
            _logger.LogInformation($"The file {fileName} will be deleted!");
            if (!File.Exists(fileName))
            {
                fileName = GetAttachmentPath(fileName);
            }

            if (File.Exists(fileName))
            {
                _logger.LogDebug($"The file {fileName} exist, then will delete");
                File.Delete(fileName);
            }

            var filePath = Path.GetDirectoryName(fileName);
            if (Directory.Exists(filePath))
            {
                //如果存在文件缩略图则删除
                var relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Thumbnail.Prefix ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"the image file {fileName} thumbnail file exist, then will delete thumbnail file");
                    File.Delete(relatedFileName);
                }

                //如果存在文件裁剪图则删除
                relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Crop.Prefix ?? AppSettings.DEFAULT_IMAGE_CROP_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"the image file {fileName} crop file exist, then will delete file crop file");
                    File.Delete(relatedFileName);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"The file {fileName} delete throw exception");
        }
    }
    /// <summary>
    /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="files">待删除的文件</param>
    public virtual void DeleteFiles(params string[] files)
    {
        if (files.Length == 0)
        {
            return;
        }

        _logger.LogInformation($"User will delete the files <{files.ToString<string>()}> from file system");
        var result = Parallel.ForEach(files, DeleteFile);
        if (result.IsCompleted)
        {
            _logger.LogInformation($"User delete the files <{files.ToString<string>()}> from file system success");
        }
    }

    /// <summary>
    /// 销毁文件, 如果销毁, 此接口将性能不好且文件则无法找回
    /// 销毁附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="fileName">待销毁的文件</param>
    public virtual async Task DestoryFileAsync(string fileName)
    {
        try
        {
            _logger.LogInformation($"The file {fileName} will be destoried");
            if (!File.Exists(fileName))
            {
                fileName = GetAttachmentPath(fileName);
            }

            if (File.Exists(fileName))
            {
                _logger.LogDebug($"The file {fileName} exist, then will be destoried");
                await FileUtility.FileDestoryAsync(fileName);
            }

            //如果存在文件缩略图则删除
            var filePath = Path.GetDirectoryName(fileName);
            if (Directory.Exists(filePath))
            {
                //如果存在文件缩略图则删除
                var relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Thumbnail.Prefix ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"the image file {fileName} thumbnail file exist, then will destory thumbnail file");
                    await FileUtility.FileDestoryAsync(relatedFileName);
                }

                //如果存在文件裁剪图则删除
                relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Crop.Prefix ?? AppSettings.DEFAULT_IMAGE_CROP_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"the image file {fileName} crop file exist, then will destory file crop file");
                    await FileUtility.FileDestoryAsync(relatedFileName);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"The file {fileName} destory throw exception");
        }
    }
    /// <summary>
    /// 销毁文件, 如果销毁, 此接口将性能不好且文件则无法找回
    /// 销毁附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="files">待销毁的文件</param>
    public virtual async Task DestoryFilesAsync(params string[] files)
    {
        if (files.Length == 0)
        {
            return;
        }

        _logger.LogInformation($"User will destory the files <{files.ToString<string>()}> from file system");
        Parallel.ForEach(files, async file => await DestoryFileAsync(file));
        await Task.CompletedTask;
    }

    /// <summary>
    /// 从上传的图片文件得到一个合适尺寸的缩略图
    /// </summary>
    /// <param name="fileName">原始文件</param>
    /// <param name="setting">缩略图设置</param>
    /// <returns></returns>
    public virtual async Task<string?> GetImageThumbnailFileAsync(string fileName, FileSetting? setting = null)
    {
        if (!File.Exists(fileName))
        {
            _logger.LogWarning($"图片文件 {fileName} 不存在");
            return null;
        }

        setting ??= _httpSetting.Attachment?.Thumbnail!;
        if (!setting.Enable)
        {
            return null;
        }

        fileName = await fileName.CreateDefaultCropImageAsync(setting.Size, setting.Prefix);
        _logger.LogInformation($"已创建图片文件缩略图: {fileName}");
        return fileName;
    }
    /// <summary>
    /// 从上传的图片文件得到一个合适尺寸的裁剪图片
    /// </summary>
    /// <param name="fileName">原始文件</param>
    /// <param name="setting">裁剪设置</param>
    /// <returns></returns>
    public virtual async Task<string?> GetImageCropFileAsync(string fileName, FileSetting? setting = null)
    {
        if (!File.Exists(fileName))
        {
            _logger.LogWarning($"图片文件 {fileName} 不存在");
            return null;
        }

        setting ??= _httpSetting.Attachment?.Crop!;
        if (!setting.Enable)
        {
            return null;
        }

        var fileInfo = new FileInfo(fileName);
        var extensions = setting.Extensions.ToUpperInvariant().Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        var extension = fileInfo.Extension.ToUpperInvariant();
        if (!extensions.Any(x => x == extension))
        {
            _logger.LogWarning($"不支持的图片类型: {fileInfo.Extension}, 仅支持: {setting.Extensions}!");
            return null;
        }

        var imageSize = setting.Size;
        var limitSize = setting.Total.GetByteSizeDetail();
        var cropImage = Path.Combine(fileInfo.DirectoryName!, setting.Prefix + Path.GetFileName(fileName));

        while (fileInfo.Length > setting.Total)
        {
            _logger.LogInformation($"图片文件大小: {fileInfo.Length.GetByteSizeDetail()} 超过限制大小: {limitSize}, 将使用较小尺寸的缩略图!");
            if (File.Exists(cropImage))
            {
                File.Delete(cropImage);
            }

            await fileName.CropImageAsync(imageSize, imageSize, cropImage);
            _logger.LogInformation($"已创建图片文件裁剪图: {cropImage}");

            if (fileInfo.Length > setting.Total)
            {
                fileName = cropImage;
                fileInfo = new FileInfo(fileName);
                imageSize = Convert.ToInt32(setting.Size * 0.75);
            }
        }

        return fileName;
    }
}